import sys

from django.contrib.auth.models         import User,Group,Permission
from django.contrib.contenttypes        import generic
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models        import Site
from django.core.exceptions             import ValidationError
from django.core.urlresolvers           import get_callable
from django.core.validators             import EMPTY_VALUES
from django.db                          import models,IntegrityError
from django.template.loader             import get_template_from_string
from django.utils.translation           import ugettext_lazy as _
from sitepal.sitebase.managers          import TreeManager
from sitepal.sitebase.managers          import EntityManager,EntityTreeManager,UnknownEntityManager
from sitepal.sitebase.managers          import ResourceManager,ResourceTreeManager,PropertyManager

ORIGIN_UNKNOWN = '[unknown]'
ORIGIN_USER    = '[user]'

class Tree(models.Model):
    parent  = models.ForeignKey('self',verbose_name=_("parent"),related_name='children_set',
                                null=True,blank=True)
    objects = TreeManager()

    class Meta:
        abstract = True

    @property
    def ancestors(self):
        if self.parent:
            current = self.parent
            ances   = list()
            while current:
                ances.append(current)
                current = current.parent
            return ances
        return []

    @property
    def level(self):
        return len(self.ancestors)

    @property
    def siblings(self):
        return self._default_manager.filter(parent=self.parent).exclude(pk=self.id)

    @property
    def children(self):
        return self.children_set.all()

    @property
    def descendants(self):
        descs = set(self.children)
        for node in list(descs):
            descs.update(node.descendants)
        return descs

class Entity(models.Model):
    site         = models.ForeignKey(Site,verbose_name=_("site"),editable=False,
                                     default=Site.objects.get_current)
    origin       = models.CharField(_("origin"),max_length=50,editable=False,default="[user]")
    creator      = models.ForeignKey(User,verbose_name=_("entity creator"),editable=False,
                                     related_name="%(class)s_creator_set")
    date_created = models.DateTimeField(_("entity creation timestamp"),editable=False,
                                        auto_now_add=True)
    owner        = models.ForeignKey(User,verbose_name=_("entity owner"),editable=False,
                                     related_name="%(class)s_owner_set")
    objects      = EntityManager()
    unknown      = UnknownEntityManager()
    
    class Meta:
        abstract = True

class EntityTree(Entity,Tree):
    objects = EntityTreeManager()

    class Meta(Entity.Meta):
        abstract = True

class Resource(Entity):
    name    = models.SlugField(_("name"),max_length=100,editable=False)
    objects = ResourceManager()

    def __unicode__(self):
        return self.name

    class Meta(Entity.Meta):
        abstract = True

class ResourceTree(Resource,Tree):
    objects = ResourceTreeManager()

    class Meta(Resource.Meta):
        abstract = True

class Property(Resource):
    value           = models.TextField(_("property's value"),max_length=8192,null=True,blank=True)
    validator_names = models.CharField(_("property's validator"),max_length=250,null=True,
                                       blank=True,editable=False)
    to_python_name  = models.CharField(_("property's coercer"),max_length=100,null=True,blank=True,
                                       editable=False)
    to_string_name  = models.CharField(_("property's coercer"),max_length=100,null=True,blank=True,
                                       editable=False)
    objects         = PropertyManager()

    class Meta(Resource.Meta):
        unique_together     = ('site','name')
        verbose_name        = _('property')
        verbose_name_plural = _('properties')

    def __unicode__(self):
        return self.name

    def _value_to_python(self,value):
        if not hasattr(self,'_value_to_python_func'):
            if self.to_python_name:
                self._value_to_python_func = get_callable(self.to_python_name)
            else:
                self._value_to_python_func = str
        return self._value_to_python_func(value)

    def _value_to_string(self,value):
        if not hasattr(self,'_value_to_string_func'):
            if self.to_string_name:
                self._value_to_string_func = get_callable(self.to_string_name)
            else:
                self._value_to_string_func = str
        return self._value_to_string_func(value)

    def _value_to_string_validate(self,value):
        if not hasattr(self,'_value_validators'):
            validators = []
            if self.validator_names:
                for name in self.validator_names.split(','):
                    validators.append(get_callable(name.strip()))
            self._value_validators = tuple(validators)
        value = self._value_to_string(value)
        for validate in self._value_validators:
            validate(value)
        return value

    def get_value(self,default=None):
        if self.value in EMPTY_VALUES:
            return default
        if not hasattr(self,'_python_value'):
            self._python_value = self._value_to_python(self.value)
        return self._python_value or default

    def set_value(self,value):
        if value in EMPTY_VALUES:
            self.value = None
        else:
            self.value         = self._value_to_string_validate(value)
            self._python_value = value

class Access(models.Model):
    user         = models.ForeignKey(User,verbose_name=_("user"),blank=True,null=True,
                                     related_name="%(class)s_user_set")
    group        = models.ForeignKey(Group,verbose_name=_("user"),blank=True,null=True)
    permission   = models.ForeignKey(Permission,verbose_name=_("permission"))
    inherits     = models.BooleanField(_("inherits"),default=False)
    grantee_type = models.ForeignKey(ContentType,editable=False)
    grantee_id   = models.PositiveIntegerField(editable=False)
    grantee      = generic.GenericForeignKey('grantee_type','grantee_id')
    site         = models.ForeignKey(Site,verbose_name=_("site"),editable=False,
                                     default=Site.objects.get_current)
    date_granted = models.DateTimeField(_("granting timestamp"),editable=False,auto_now_add=True)
    granter      = models.ForeignKey(User,verbose_name=_("granter"),editable=False,
                                     related_name="%(class)s_granter_set")

    class Meta:
        verbose_name        = _('access')
        verbose_name_plural = _('accesses')
        unique_together     = ('user','group','permission','grantee_type','grantee_id','site')

    def __unicode__(self):
        subject = "user {0}".format(self.user.username) if self.user else "group {0}".format(self.group.name)
        return "{subject} has {perm} on {app}.{model} pk={obj}".format(subject=subject,
               perm=self.permission.codename,app=self.grantee_type.app_label,
               model=self.grantee_type.model,obj=self.grantee_id)

    def clean(self):
        if (self.user and self.group) or not (self.user or self.group):
            raise ValidationError,_("Either user or group must be set")

    def save(self,*args,**kwargs):
        if (self.user and self.group) or not (self.user or self.group):
            raise IntegrityError,"Either user or group must be set"
        super(Access,self).save(*args,**kwargs)
